#include "defines.h"
#include "ipc.h"
#include "memory.h"
#include "lib.h"
#include "kozos.h"
#include "kernel.h"

typedef struct _kz_msgbuf {
    struct _kz_msgbuf *next;
    kz_thread *sender;
    struct {
        int size;
        char *p;
    } param;
} kz_msgbuf;

typedef struct _kz_msgbox {
    int engaged;
    kz_thread *receiver; // ボックスごとに受信者は1人とする。
    kz_msgbuf *head;
    kz_msgbuf *tail;
} kz_msgbox;

#define MSGBOX_NUM   16

static kz_msgbox msgboxes[MSGBOX_NUM];

void resource_msgbox()
{
    int i, c = 0;

    for (i = 0; i < MSGBOX_NUM; i++) {
        if (msgboxes[i].engaged) {
            ++c;
        }
    }

    puts("MSGBOX: ");
    putval(c, 0);
    puts(" / ");
    putval(MSGBOX_NUM, 0);
    puts("\n");
}

void ipc_init()
{
    memset(msgboxes, 0, sizeof(msgboxes));
}

kz_msgbox_id_t msgbox_get()
{
    int i;
    for (i = 0; i < MSGBOX_NUM; i++) {
        if (!msgboxes[i].engaged) {
            msgboxes[i].engaged = 1;
            return i;
        }
    }

    kz_sysdown("msgbox_get: cannot get msgbox");
    return MSGBOX_NULL;
}

void msgbox_free(kz_msgbox_id_t id)
{
    kz_msgbox *mb = &msgboxes[id];

    if (!mb->engaged)
        kz_sysdown("msgbox_free: msgbox is not used");
    if (mb->head) // 単に解放ではメモリリークが防げない
        kz_sysdown("msgbox_free: msgbox is not empty");

    if (mb->receiver) {
        sys_wakeup((kz_thread_id_t)mb->receiver);
    }

    memset(mb, 0, sizeof(*mb));
}

kz_thread *sendmsg(kz_msgbox_id_t id, kz_thread *sender, int size, char *p)
{
    kz_msgbuf *mp;
    kz_msgbox *mboxp = &msgboxes[id];

    if (!mboxp->engaged)
        kz_sysdown("sendmsg: msgbox is not used");

    mp = (kz_msgbuf *)kzmem_alloc(sizeof(*mp));
    if (!mp) kz_sysdown("sendmsg: memory allocation failed");
    mp->next = NULL;
    mp->sender = sender;
    mp->param.size = size;
    mp->param.p = p;

    if (mboxp->tail) {
        mboxp->tail->next = mp;
    } else {
        mboxp->head = mp;
    }
    mboxp->tail = mp;

    return mboxp->receiver;
}

kz_thread_id_t recvmsg(kz_msgbox_id_t id, kz_thread *receiver)
{
    kz_msgbox *mboxp = &msgboxes[id];
    kz_msgbuf *mp;
    kz_syscall_param_t *param = receiver->syscall.param;

    if (!mboxp->engaged)
        kz_sysdown("recvmsg: msgbox is not used");
    if (mboxp->receiver && (receiver != mboxp->receiver))
        kz_sysdown("recvmsg: receiver already exists");

    if (mboxp->head) {
        mp = mboxp->head;
        mboxp->head = mp->next;
        if (!mboxp->head)
            mboxp->tail = NULL;
        mp->next = NULL;

        param->un.recv.ret = (kz_thread_id_t)mp->sender;
        if (param->un.recv.sizep)
            *param->un.recv.sizep = mp->param.size;
        if (param->un.recv.pp)
            *param->un.recv.pp = mp->param.p;

        kzmem_free(mp);

    } else {
        param->un.recv.ret = -1;
    }

    receiver->flags &= ~THREAD_WAITMSG;
    mboxp->receiver = NULL;

    return param->un.recv.ret;
}

int msgbox_arrived(kz_msgbox_id_t id, kz_thread *receiver)
{
    kz_msgbox *mboxp = &msgboxes[id];

    if (!mboxp->engaged)
        kz_sysdown("msg_arrived: msgbox is not used");
    if (mboxp->receiver && (receiver != mboxp->receiver))
        kz_sysdown("msgbox_arrived: receiver already exists");

    return (mboxp->head != NULL);
}

int msgbox_setreceiver(kz_msgbox_id_t id, kz_thread *receiver)
{
    kz_msgbox *mboxp = &msgboxes[id];

    if (!mboxp->engaged)
        kz_sysdown("msgbox_setreceiver: msgbox is not used");
    if (mboxp->receiver)
        kz_sysdown("msgbox_setreceiver: receiver already exists");

    receiver->flags |= THREAD_WAITMSG;
    mboxp->receiver = receiver;

    return 0;
}
